/**
 * 
 */
package edu.ust.seis610;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;

import edu.ust.seis610.data.DataPoint;
import edu.ust.seis610.data.TrainingData;
import edu.ust.seis610.function.Function;
import edu.ust.seis610.twitter.Tweet;
import edu.ust.seis610.utils.DateUtils;
import edu.ust.seis610.utils.NodeUtils;
import edu.ust.seis610.chart.DynamicChart;


/**
 * @author meeusen
 *
 */
@SuppressWarnings("unchecked")
public class FunctionRunner
{
	public Function findTargetFunction( boolean tweetResults, boolean createTrainingData, int initPopulationSize,
			double mutationPercent, double crossOverPercent, DynamicChart chart, int timeoutMin) throws Exception
	{
//		take initial time stamp, we'll use this to determine if 15 minutes has elasped later
		long startTm=System.currentTimeMillis();
		
		Double numCross, numMutate=0.0;
		Function smallFunc = null;
//		validate that the crossover and muatation percentage isn't too high
		if(mutationPercent+crossOverPercent>=100)
			throw new Exception("mutation and crossOver percentage are >=100, reduce these properties and re-run");
		else
		{
			numCross=(crossOverPercent/100)*initPopulationSize;
//			crossover requires two functions, so we need an even number here
//			if its not even, subtract 1 to make it even
			if (numCross%2!=0)
				numCross--;
			numMutate=(mutationPercent/100)*initPopulationSize;
		}
		try
		{
//			tweet start run message
			if(tweetResults)
				Tweet.tweetMessage("Starting Genetic Program Run "+ DateUtils.javaCalendar2String(Calendar.getInstance()));
			
			int popSize=0;
			popSize = initPopulationSize;
			
			if (createTrainingData)
				TrainingData.createInitialXMLFile();
		
			ArrayList<DataPoint> dp =TrainingData.getTrainingData();
			ArrayList<Function> funAl =new ArrayList<Function>();
			double smallest = 999999999;
			smallFunc= new Function();
			for(int x=0;x<popSize;x++)
			{
				Function aFun= new Function();
				aFun.evaluateFitnessValue(dp);
//				add the function to the function arraylist
				funAl.add(aFun);
	
				if(aFun.getFitnessValue()<smallest)
				{
					smallest=aFun.getFitnessValue();
					smallFunc=aFun;
				}
			}
			System.out.println("smallest fitness: "+ Double.toString(smallest)+ "   For f(x)="+smallFunc.toString());
			chart.graph1(0.0, smallFunc.toString(),smallFunc.getFitnessValue(), "1");
			
//			sort the functions in ascending fitness order (the best function will be at pos(0))
			Collections.sort(funAl);
			boolean funcFound =false;
			int genCnt=1;
			double genAvg =0.0;
			Comparator<Function> rerv=Collections.reverseOrder();
			String timeEl=null;
			float elaspMin=(System.currentTimeMillis()-startTm)/(60*1000F);
			while(!funcFound && elaspMin<timeoutMin)
			{
				genAvg=0.0;
				genCnt++;
				Collections.sort(funAl,rerv);
				performCrossOverOnPopulation(numCross,funAl);
				performMutationOnPopulation(numMutate,(int) (numCross+1),funAl);
				
				for(Function fun:funAl)
				{
					fun.evaluateFitnessValue(dp);
					if(fun.getFitnessValue()<smallest)
					{
						smallest=fun.getFitnessValue();
						smallFunc=fun;
					}
					if(fun.getFitnessValue()==0.0)
						funcFound=true;
					
					if((!Double.isNaN(fun.getFitnessValue())) && (!Double.isInfinite(fun.getFitnessValue())))
						genAvg=genAvg+fun.getFitnessValue();
				}
//				update the elasped minutes so that the counter increments
				elaspMin=(System.currentTimeMillis()-startTm)/(60*1000F);
				int eMin=(int)elaspMin;
				timeEl = "Time elapsed: "+eMin+":"+Float.toString(((System.currentTimeMillis()-startTm)/(1000F)) % 60);
				
				genAvg=(genAvg/funAl.size());
				chart.graph1(genAvg, smallFunc.toString(),smallFunc.getFitnessValue(), Integer.toString(genCnt),timeEl);
				
				System.out.println("---------------------------");
				System.out.println("Generation: "+Integer.toString(genCnt));
				System.out.println("Function with best fitness is F(x)="+smallFunc.toString()+
						"     Fitness:"+smallFunc.getFitnessValue());
				System.out.println("avg generational fitness: "+Double.toString(genAvg));
				Calendar runTime =Calendar.getInstance();
				runTime.setTimeInMillis(System.currentTimeMillis()-startTm);
				System.out.println(timeEl);
			}
//			tweet end run message
			if(tweetResults)
				Tweet.tweetMessage("Finished Genetic Program Run.  "+ timeEl+  
					" best function was f(x)="+smallFunc.toString()+" with a fitness: "+ Double.toString(smallest));
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return smallFunc;
	}

	private void performMutationOnPopulation(Double numMutate, Integer startIndex,
			ArrayList<Function> funAl) throws Exception
	{	
		int startPos=startIndex;
		for(int i=startPos; i<(numMutate+startPos); i++)
		{
//			System.out.println("mutating function at:"+Integer.toString(i));
			NodeUtils.mutateFunction(funAl.get(i));
			
		}		
	}

	private void performCrossOverOnPopulation(Double numCross,
			ArrayList<Function> funAl)
	{	
		for(int i =0;i<numCross;i=i+2)
		{
//			System.out.println("CrossingOver functions at:"+Integer.toString(i)+ " and:"+Integer.toString((i+1)));
			NodeUtils.crossoverFunctions(funAl.get(i), funAl.get(i+1));
		}	
	}
}
